package parser;

/*
 * Parser for add
 * Takes in command from GUI.Main, and call appropriate method in add
 * 
 */

import java.util.Calendar;
import java.util.GregorianCalendar;

import logger.Logger;
import logic.Add;
import logic.DateTime;
import memory.Memory;

public class ParserAdd extends ParserMain {

	
	
	private static final String LOGGER_INVALID_COMMAND_IN_ADD = "Invalid command in add";
	//Time
	private static final String MINUTE_BEFORE_MIDNIGHT = "2359";
	private static final String MIDNIGHT = "0000";

	//Symbol
	private static final String SPACE = " ";
	private static final String COMMA = ",";
	private static final String COLON = ":";

	//Command
	private static final String NEXT = "next";
	private static final String THIS = "this";
	private static final String AT = "at";

	//Day
	private static final int MONDAY = 0;
	private static final int SUNDAY = 6;
	private static final int YESTERDAY = 10;
	private static final int TODAY = 11;
	private static final int TOMORROW = 12;

	private static final String ERROR_TASKNAME_IS_NUMERIC = "Task name with numbers only are disallowed.";
	private static final String ERROR_END_DATE_BEFORE_START_DATE = "End date cannot be before start date!";
	private static final String ERROR_ADDING_TASK = "Error adding task, please try again!";
	private static final String ERROR_ADDING_DEADLINE = "Error adding deadline, please try again!";
	private static final String ERROR_ADDING_EVENT = "Error adding event, please try again!";
	private static final String ERROR_INVALID_ADD_TYPE = "You may only add a task, a deadline or an event.";

	private static final String TASK = "Task";
	private static final String DEADLINE = "Deadline";
	private static final String EVENT = "Event";

	private static final int ADD_EVENT_BOTH_START_AND_END = 4;
	private static final int ADD_EVENT_START_DATE_ONLY = 3;
	private static final int ADD_DEADLINE = 2;
	private static final int ADD_TASK = 1;
	
	private static final int INVALID = -1;

	private static Calendar start;
	private static Calendar end;

	//@author A0085082E
	/*
	 * Initialising variables
	 */
	private static void initVar(){
		setTaskName(null);
		setDescription(null);
		start=  new GregorianCalendar();
		end=  new GregorianCalendar();
		setType(INVALID);
		setLength(INVALID);
		setContainsDescription(false);
		setProper(true);
	}

	//@author A0085082E
	/*
	 * Takes in command from GUI.Main and run.
	 */
	public static void run(String command, Memory memory){
		initVar();
		setCommand(command);
		removeAdditionalSpacesInCommand();
		runCodeIfCommandIsValid(command, memory);

	}

	//@author A0085082E
	/*
	 *  Check that command is not empty, else generate error message.
	 */
	private static void runCodeIfCommandIsValid(String command, Memory memory) {
		if(isUselessCommand(command)){
			generalError();
			Logger.add(LOGGER_INVALID_COMMAND_IN_ADD);
		}else{
			add(memory);
			
		}
	}

	//@author A0085082E
	/*
	 * Takes in command and call appropriate method in add.
	 */
	private static void add(Memory memory){

		String[] details = command.split(COMMA);

		setAddType(details);
		setContainsDescription(commandContainsDescription(details));
		setAddTaskName(details);
		setProper(taskNameIsSet(taskName));
		setToDisallowNumericTaskName();
		callVariousAddMethods(memory, details);

	}

	//@author A0085082E
	/*
	 * Check type of item that user wants to add and run command
	 */
	private static void callVariousAddMethods(Memory memory, String[] details) {
		switch (type) {
		case ADD_EVENT_START_DATE_ONLY:
			addEventWithStartDateOnly(memory, details);
			break;

		case ADD_EVENT_BOTH_START_AND_END:
			addEventWithBothStartAndEndDate(memory, details);
			break;

		case ADD_DEADLINE:
			addDeadline(memory, details);
			break;

		case ADD_TASK:
			addTask(memory, details);
			break;

		default:

			System.out.println(ERROR_INVALID_ADD_TYPE);
			Logger.add(LOGGER_ERROR + ERROR_INVALID_ADD_TYPE);
		}
	}

	//@author A0085082E
	/*
	 * Check if details for task is set properly, then call add if set proper
	 * Else return error message
	 */
	private static void addTask(Memory memory, String[] details) {
		if(!setProper){
			System.out.println(ERROR_ADDING_TASK);
			Logger.add(LOGGER_ERROR + ERROR_ADDING_TASK);

		}else{
			setAddDescription(details, type);
			setDescriptionForAddTask(memory);
		}
	}

	//@author A0085082E
	/*
	 * Send variables to add task depending on whether user input contain description
	 */
	private static void setDescriptionForAddTask(Memory memory) {
		if(containsDescription){
			Add.addToMemory(TASK, taskName, description, start, end, memory);
		}else{
			Add.addToMemory(TASK, command, null, start, end, memory);
		}
	}

	//@author A0085082E
	/*
	 * Check if details for deadline is set properly, then call add if set proper
	 */
	private static void addDeadline(Memory memory, String[] details) {
		if(!setAddDeadline(details[length-1])||!setProper){
			System.out.println(ERROR_ADDING_DEADLINE);
			Logger.add(LOGGER_ERROR + ERROR_ADDING_DEADLINE);

		}else{
			setAddDescription(details, type);
			Add.addToMemory(DEADLINE, taskName, description, start, end, memory);
		}
	}

	//@author A0085082E
	/*
	 * Check if details for event with both start and end date is set properly
	 * if so, then call add 
	 * Else return error message
	 */
	private static void addEventWithBothStartAndEndDate(Memory memory,
			String[] details) {
		if(!setAddEventVariableWithStartAndEnd(details)||!setProper){
			System.out.println(ERROR_ADDING_EVENT);
			Logger.add(LOGGER_ERROR + ERROR_ADDING_EVENT);
		}else{
			if(endIsBeforeStart(start, end)){
				System.out.println(ERROR_END_DATE_BEFORE_START_DATE);
				Logger.add(LOGGER_ERROR + ERROR_END_DATE_BEFORE_START_DATE);
			}else{
				setAddDescription(details, type);
				Add.addToMemory(EVENT, taskName, description, start, end, memory);
			}
		}
	}

	//@author A0085082E
	/*
	 * Check if details for event with start date only is set properly
	 * if so, then call add 
	 * Else return error message
	 */
	private static void addEventWithStartDateOnly(Memory memory,
			String[] details) {
		if(!setAddEventVariableWithStartOnly(details)||!setProper){
			System.out.println(ERROR_ADDING_EVENT);
			Logger.add(LOGGER_ERROR + ERROR_ADDING_EVENT);
		}else{
			setAddDescription(details, type);
			Add.addToMemory(EVENT, taskName, description, start, end, memory);
		}
	}

	//@author A0085082E
	/*
	 * Checks if user's input contain description
	 * Returns true if it does.
	 */
	private static boolean commandContainsDescription(String[] details){

		switch(type){

		case ADD_EVENT_BOTH_START_AND_END:
			return checkForColon(details, details.length-2);
		case ADD_EVENT_START_DATE_ONLY:
			return checkForColon(details, details.length-1);
		case ADD_DEADLINE:
			return checkForColon(details, details.length-1);
		case ADD_TASK:
			return checkForColon(details, details.length);
		default:
			return false;
		}
	}

	//@author A0085082E
	/*
	 * Check array of string for colon.
	 * If any of the string contain a colon, returns true.
	 */
	private static boolean checkForColon(String[] details, int endIndex){
		for(int i = 0; i < endIndex; i++){
			if(details[i].contains(COLON)){
				return true;
			}
		}
		return false;
	}

	//@author A0085082E
	/*
	 * Set task name for item that user wants to add.
	 */
	private static void setAddTaskName(String[] details){
		if(containsDescription){
			String newName = command.substring(0, command.indexOf(COLON));
			setTaskName(newName.trim());
		}else{
			setTaskName(setTaskNameNoDescription(details));
		}
	}

	//@author A0085082E
	/*
	 * This method set type for all events:
	 * 
	 * 1. Deadline: If the last fragment of user input contains 'by'
	 * 2. Event: If second last fragment contains "on" and last fragment contains "to"
	 * 3. Task: Any other forms 
	 */
	private static void setAddType(String[] details){

		String[] temp = null, temp2 = null;
		length = details.length;
		if(length >=2){
			temp = details[length-2].trim().split(SPACE);
			temp2 = details[length-1].trim().split(SPACE);
		}

		if(length >= 3 && isStartCommand(temp[0]) && isEndCommand(temp2[0])){
			setType(ADD_EVENT_BOTH_START_AND_END);
		}else if(length >= 2 && isStartCommand(temp2[0])){
			setType(ADD_EVENT_START_DATE_ONLY);
		}else if(length>= 2 && !isStartCommand(temp[0]) && isEndCommand(temp2[0])){ 
			setType(ADD_DEADLINE);
		}else{
			setType(ADD_TASK);
		}

	}

	//@author A0085082E
	/*
	 * Set start date/time and end date/time for item of type event.
	 * Method returns true if the date and time are set successfully.
	 */
	private static boolean setAddEventVariableWithStartAndEnd(String[] details){

		Boolean setStart, setEnd;

		setStart = setAddEventEndDateTime(details[length-1]);
		setEnd = setAddEventStartDateTime(details[length-2]);  

		return(start.isSet(Calendar.DATE)&&end.isSet(Calendar.DATE)&&setStart&&setEnd);

	}

	//@author A0085082E
	/*
	 * Set start date/time for event with start time only.
	 * Method returns true if the date and time are set successfully.
	 */
	private static boolean setAddEventVariableWithStartOnly(String[] details){

		Boolean setStart, setEnd;

		setStart = setAddEventEndDateTime(details[length-1]);
		setEnd = setAddEventStartDateTime(details[length-1]);  

		return(start.isSet(Calendar.DATE)&&end.isSet(Calendar.DATE)&&setStart&&setEnd);

	}

	//@author A0085082E
	/*
	 * Set end date/time for item of type deadline.
	 * Method returns true if the date and time are set successfully.
	 */
	private static boolean setAddDeadline(String temp){
		boolean setDeadline = false;

		setDeadline = setAddEventEndDateTime(temp);

		return(taskName != null&&end.isSet(Calendar.DATE)&&setDeadline);
	}

	//@author A0085082E
	/*
	 * Set description for item if containsDescription previously set is true.
	 */
	private static void setAddDescription(String[] details, int type){
		if(containsDescription){
			setDescription(details,type);
		}
	}

	//@author A0085082E
	/*
	 * Set task name for items with no description
	 */
	private static String setTaskNameNoDescription(String[] details){
		String newName = details[0];
		switch (type){
		case ADD_DEADLINE:
			for(int i = 1; i < length-1; i++){
				newName = newName + COMMA + details[i]; 
			}
			break;
		case ADD_EVENT_START_DATE_ONLY:
			for(int i = 1; i < length-2; i++){
				newName = newName + COMMA + details[i]; 
			}
			break;
		case ADD_TASK: 
			newName = command;
			break;
		}
		return newName;
	}

	//@author A0085082E
	/*
	 * Checks if task name has been set
	 * If it has, returns true.
	 */
	private static boolean taskNameIsSet(String taskName){
		if(taskName.length() == 0){
			return false;
		}else{
			return true;
		}
	}

	//@author A0085082E
	/*
	 * To toggle setProper to false if task name contains number only.
	 * Send an error message to user to prompt user not to have full numeric task name
	 */
	private static void setToDisallowNumericTaskName(){
		if(setProper){
			if(removeAllInt(taskName).length()==0){
				setProper(false);
				System.out.println(ERROR_TASKNAME_IS_NUMERIC);
			}
		}

	}

	//@author A0085082E
	/*
	 * Set end date of event and default end time of 2359
	 * Returns true if date and time are set
	 */
	private static Boolean setAddEventEndDateTime(String endString) {
		boolean setEnd = false;

		endString = endString.trim();
		String[] details = endString.split(SPACE);

		setEnd = setDateAndTime(details, end, MINUTE_BEFORE_MIDNIGHT);

		return setEnd;
	}

	//@author A0085082E
	/*
	 * Set non numeric date for items that is a week later
	 */
	private static Calendar setNonNumericDateForNextWeek(String details, Calendar cal){
		cal = setNonNumericDate(details,cal);
		cal = DateTime.nextWeek(cal);

		return cal;
	}

	//@author A0085082E
	/*
	 * Set start date of event and default start time of 0000
	 * Returns true if date and time are set
	 */
	private static Boolean setAddEventStartDateTime(String startString) {
		boolean setStart = false;

		startString = startString.trim();
		String[] details = startString.split(SPACE);

		setStart = setDateAndTime(details, start, MIDNIGHT);

		return setStart;
	}

	//@author A0085082E
	/*
	 * This method takes in an array of string of details, a calendar to set details to, and a default time.
	 * returns true if dates and time are set
	 */
	private static boolean setDateAndTime(String[] details, Calendar cal, String defaultTime){
		boolean set = false;
		if(dateIsNumericAndContainsTime(details)){
			set = setDate(details[1],cal)&& setTime(details[3],cal);

		}else if(dateIsNonNumericAndContainsTimeType1(details)){
			cal = setNonNumericDate(details[2], cal);
			set = setTime(details[4],cal);

		}else if(dateIsNonNumericAndContainsTimeType2(details)){
			cal = setNonNumericDate(details[1], cal);
			set = setTime(details[3],cal);

		}else if(dateIsNonNumericButDoesNotContainTimeType1(details)){
			cal = setNonNumericDate(details[1], cal);
			set = setTime(defaultTime, cal);

		}else if(dateIsNonNumericButDoesNotContainTimeType2(details)){
			cal = setNonNumericDate(details[2], cal);
			set = setTime(defaultTime, cal);

		}else if(dateIsNonNumericWeekLaterButDoesNotContainTime(details)){
			cal = setNonNumericDateForNextWeek(details[2], cal);
			set = setTime(defaultTime, cal);

		}else if(dateIsNonNumericWeekLaterAndContainTime(details)){
			cal = setNonNumericDateForNextWeek(details[2], cal);
			set = setTime(details[4],cal);

		}else if(dateIsNumericButDoesNotContainTime(details)){
			set = setDate(details[1],cal)&&setTime(defaultTime, cal);
		}

		return set;
	}

	//@author A0085082E
	/*
	 * Takes in a string of detail that is the non-numeric date that user wish to add
	 * and set it to calendar
	 */
	private static Calendar setNonNumericDate(String details, Calendar cal){
		int date = DateTime.isDayOfWeek(details);
		int today = DateTime.getDayOfTheWeek(cal);
		if(date != TODAY ){
			if(date>= MONDAY && date<= SUNDAY){
				cal = DateTime.addNumberOfDays(cal, (date - today + 7) % 7);
			}else if(date == TOMORROW){
				cal = DateTime.tomorrow(cal);
			}else if(date == YESTERDAY){
				cal = DateTime.addNumberOfDays(cal, INVALID);
			}
		}

		return cal;
	}

	//@author A0085082E
	/*
	 * Set description
	 * Check that taskName is already set
	 * Trim description string and set according to type
	 */
	private static void setDescription(String[] details, int type){
		if(taskName!= null){
			int count = findIndexWithColon(details);
			description = details[count].substring(details[count].indexOf(COLON)+1);
			description = description.trim();
			setDescriptionAccordingToType(details, type, count);

		}
	}

	//@author A0085082E
	/*
	 * Set description according to the types
	 */
	private static void setDescriptionAccordingToType(String[] details,
			int type, int count) {
		switch (type){
		case ADD_DEADLINE:
			for(int i = count+1; i < length-1; i++){
				description = description + COMMA + details[i]; 
			}
			break;
		case ADD_EVENT_START_DATE_ONLY:
			for(int i = count+1; i < length-2; i++){
				description = description + COMMA + details[i]; 
			}
			break;
		case ADD_TASK: 
			for(int i = count+1; i < length; i++){
				description = description + COMMA + details[i]; 
			}
			break;
		}
	}

	//@author A0085082E
	/*
	 * find the index of the string that contain the first colon
	 */
	private static int findIndexWithColon(String[] details){
		int count = 0;
		while(count < length && !details[count].contains(COLON)){
			count++;
		}
		return count;
	}

	//@author A0085082E
	private static boolean dateIsNumericAndContainsTime(String[] details){
		return details.length == 4 && details[2].equals(AT) && !(DateTime.isNonNumericDateNotWeekDay(details[1])||DateTime.isNonNumericDateWeekDay(details[1]));
	}

	//@author A0085082E
	private static boolean dateIsNumericButDoesNotContainTime(String[] details){
		return details.length == 2 && !(DateTime.isNonNumericDateWeekDay(details[1])||DateTime.isNonNumericDateNotWeekDay(details[1]));
	}

	//@author A0085082E
	private static boolean dateIsNonNumericAndContainsTimeType1(String[] details){
		return details.length == 5 && details[1].toLowerCase().equals(THIS) && DateTime.isNonNumericDateWeekDay(details[2])
				&& details[3].equals(AT);
	}

	//@author A0085082E
	private static boolean dateIsNonNumericAndContainsTimeType2(String[] details){
		return details.length == 4 && (DateTime.isNonNumericDateWeekDay(details[1])|| DateTime.isNonNumericDateNotWeekDay(details[1]))&& details[2].equals(AT);
	}

	//@author A0085082E
	private static boolean dateIsNonNumericButDoesNotContainTimeType1(String[] details){
		return details.length == 2 && (DateTime.isNonNumericDateWeekDay(details[1])||DateTime.isNonNumericDateNotWeekDay(details[1]));
	}

	//@author A0085082E
	private static boolean dateIsNonNumericButDoesNotContainTimeType2(String[] details){
		return details.length == 3 && details[1].toLowerCase().equals(THIS) &&DateTime.isNonNumericDateWeekDay(details[2]);
	}

	//@author A0085082E
	private static boolean dateIsNonNumericWeekLaterButDoesNotContainTime(String[] details){
		return details.length == 3 && details[1].toLowerCase().equals(NEXT) && DateTime.isNonNumericDateWeekDay(details[2]);
	}

	//@author A0085082E
	private static boolean dateIsNonNumericWeekLaterAndContainTime(String[] details){
		return details.length == 5 && details[1].toLowerCase().equals(NEXT) && DateTime.isNonNumericDateWeekDay(details[2])
				&& details[3].equals(AT);
	}


}
